"""
1 什么是回归问题：目标值是连续型的数据，用回归算法解决回归问题，
使用回归算法训练学习数据得出一个线性模型（描述特征值和目标值之间的关系的模型），
特征值和目标值之间的关系有线性关系和非线性关系（曲线关系）两种。



2 什么是线性回归
线性回归(Linear regression)是利用回归方程(函数)对一个或多个自变量(特征值)和因变量(目标值)之间关系进行建模的一种分析方式。
一般函数：y(w) = w1x1 + w2x2 + w3x3 + ... + b

用于训练模型的数据集称为训练集，输入变量用小写x表示，称为特征或输入特征，输出目标用y表示。
经过训练集的训练后会产生一个函数y(w) = w1x1 + w2x2 + w3x3 + ... + b 这个函数称为线性模型。w1 w2 w3...称为模型的参数、系数或权重,b称为模型的偏置
我们的目标就是求出模型的参数，使模型尽可能准确。

3 损失函数
假设一个房子的线性模型如下所示：
真实关系：真实房子价格 = 0.02×中心区域的距离 + 0.04×城市一氧化氮浓度 + (-0.12×自住房平均房价) + 0.254×城镇犯罪率

那么现在呢，我们随意指定一个关系（猜测）
随机指定关系：预测房子价格 = 0.25×中心区域的距离 + 0.14×城市一氧化氮浓度 + 0.42×自住房平均房价 + 0.34×城镇犯罪率

请问这样的话，会发生什么？真实结果与我们预测的结果之间是不是存在一定的误差呢？类似这样样子

我们把这个误差用一个函数表示，这个函数就是损失函数：
J(θ) = (y(x1) - y1) ^ 2 + (y(x2) - y2) ^ 2 + ... + (y(xm) - ym) ^ 2
     = m∑i=1(y(xi) - yi) ^ 2

y(xi)是第i个训练样本下的预测值
yi是第i个训练样本下的真实值
又称最小二乘法
如何去减少这个损失，使我们预测的更加准确些？既然存在了这个损失，我们一直说机器学习有自动学习的功能，在线性回归这里更是能够体现。
这里可以通过一些优化方法（机器学习）去优化（其实是数学当中的求导功能）回归的总损失！！！

4 优化方法
优化方法就是要求模型中的参数w1 w2 w3...使得损失最小，求当损失最小时对应的参数w1 w2 w3...
线性回归经常使用的两种优化算法：正规方程、梯度下降
1）正规方程
    直接求出最好结果（天才）
    W = (X^T * X) ^ -1 * X^T * y
    理解：X为特征值矩阵，y为目标值矩阵。正规方程法只需要通过解方程的方法，就可以得到最优化结果
    缺点：正规方程要求计算(XTX)-1这一项，计算这一项需要O(n³)，而且要是n太大的话，计算的还会很慢的。
    总之一句话，当特征n的数量在1000以内的话，是可以尝试考虑正规方程的，正规方程的应用面也不是很多，像逻辑回归算法，并不能使用正规方程法。对于那些更复杂的学习算法，我们将不得不仍然使用梯度下降法。
2）梯度下降
    勤奋努力的人，是通过一次次尝试迭代找到使损失函数最小的参数w
    理解：α为学习速率，需要手动指定（超参数），α旁边的整体表示方向

    沿着这个函数下降的方向找，最后就能找到山谷的最低点，然后更新W值

    使用：面对训练数据规模十分庞大的任务 ，能够找到较好的结果

所以有了梯度下降这样一个优化算法，回归就有了"自动学习"的能力，能自动一次次迭代寻找最优结果。

5 线性回归算法API
sklearn.linear_model.LinearRegression(fit_intercept=True)
    通过正规方程优化
    fit_intercept：是否计算偏置（b的值）
    LinearRegression.coef_：回归系数，这个属性可以查看模型的参数w
    LinearRegression.intercept_：偏置,这个属性可以查看模型的偏置b

sklearn.linear_model.SGDRegressor(loss="squared_loss", fit_intercept=True, learning_rate ='invscaling', eta0=0.01)
    SGDRegressor类实现了随机梯度下降学习，它支持不同的loss函数和正则化惩罚项来拟合线性回归模型。
    loss:损失类型
        loss=”squared_loss”: 普通最小二乘法
    fit_intercept：是否计算偏置
    learning_rate : string, optional
        学习率填充
        'constant': eta = eta0，每次迭代的步长固定
        'optimal': eta = 1.0 / (alpha * (t + t0)) [default]
        'invscaling': eta = eta0 / pow(t, power_t) ，每次迭代的步长由大变小
        power_t=0.25:存在父类当中
        对于一个常数值的学习率来说，可以使用learning_rate=’constant’ ，并使用eta0来指定学习率。
    SGDRegressor.coef_：回归系数
    SGDRegressor.intercept_：偏置
sklearn提供给我们两种实现的API， 可以根据选择使用


6 线性模型性能评估
均方误差(Mean Squared Error)MSE)评价机制：
MSE = 1/m * m∑i=1(y^i - ¯y) ^ 2

线性回归评估
注：y^i为预测值，¯y为真实值

sklearn.metrics.mean_squared_error(y_true, y_pred)
均方误差回归损失
y_true:真实值
y_pred:预测值
return:浮点数结果

7  正规方程和梯度下降对比
梯度下降	            正规方程
需要选择学习率	    不需要
需要迭代求解	        一次运算得出
特征数量较大可以使用	需要计算方程，时间复杂度高O(n3)

选择：
小规模数据：
    LinearRegression(正规方程，不能解决拟合问题，所以用的比较少)
    岭回归
大规模数据：SGDRegressor(梯度下降）

8 梯度下降算法优化器
GD
    梯度下降(Gradient Descent)，原始的梯度下降法需要计算所有样本的值才能够得出梯度，计算量大，所以后面才有会一系列的改进。

SGD
    随机梯度下降(Stochastic gradient descent)是一个优化方法。它在一次迭代时只考虑一个训练样本。
    SGD的优点是：
        高效
        容易实现
    SGD的缺点是：
        SGD需要许多超参数：比如正则项参数、迭代数。
        SGD对于特征标准化是敏感的。

SAG
    随机平均梯度法(Stochasitc Average Gradient)，由于收敛的速度太慢，有人提出SAG等基于梯度下降的算法
    Scikit-learn：SGDRegressor、岭回归、逻辑回归等当中都会有SAG优化


9 欠拟合和过拟合
线性回归模型的两个问题
欠拟合：一个模型在训练数据上不能较好的拟合，在测试数据上也不能较好的拟合
过拟合：一个模型在训练数据上能较好的拟合，在测试数据上不能较好的拟合

产生的原因解决方法：
欠拟合：模型太简单了，学习到数据的特征过少，没有办法充分学习到数据中的特征
    解决办法：增加数据的特征数量，进行特征工程提取更多有效的特征
过拟合：模型太复杂，过度学习训练数据，线性模型过度拟合训练数据
    解决办法：
        1）正则化，在这里针对回归，我们选择了正则化。但是对于其他机器学习算法如分类算法来说也会出现这样的问题，除了一些算法本身作用之外（决策树、神经网络），我们更多的也是去自己做特征选择，包括之前说的删除、合并一些特征
            损失函数 + λ惩罚项
            正则化类别：
                L2正则化（比较常用）
                    作用：可以使得其中一些W的都很小，都接近于0，削弱某个特征的影响
                    优点：越小的参数说明模型越简单，越简单的模型则越不容易产生过拟合现象
                    Ridge回归（岭回归）
                L1正则化
                    作用：可以使得其中一些W的值直接为0，删除这个特征的影响
                    LASSO回归
            总结：
                正则化力度越大，权重系数会越小
                正则化力度越小，权重系数会越大
        2）特征选择：选择与目标变量相关性较高的特征，减少噪声和不相关的特征对模型的影响。

10 线性回归算法改进-岭回归
带有L2正则化的线性回归-岭回归，其实也是一种线性回归。只不过在算法建立回归方程时候，加上L2正则化的限制，从而达到解决过拟合的效果
1）API
sklearn.linear_model.Ridge(alpha=1.0, fit_intercept=True,solver="auto", normalize=False)
    alpha:正则化力度，也叫 λ
        λ取值：0~1 1~10
    solver:会根据数据自动选择优化方法
        sag优化器:如果数据集、特征都比较大，选择该随机梯度下降优化
    normalize:数据是否进行标准化（这个在新的api里面删除了这个参数）
        normalize=False:可以在fit之前调用preprocessing.StandardScaler标准化数据
    max_iter=None：迭代次数
    Ridge.coef_:回归权重
    Ridge.intercept_:回归偏置

11 逻辑回归
    1）定义
        逻辑回归（Logistic Regression）是机器学习中的一种分类模型，逻辑回归是一种分类算法，名字中带有回归，因为它与回归之间有一定的联系。
    2）逻辑回归的原理
        线性回归的输出就是逻辑回归的输入，将线性回归的输出输入到sigmoid函数中计算的结果作为逻辑回归的结果。
        假设有两个类别A，B，并且假设我们的概率值为属于A(1)这个类别的概率值。现在有一个样本的输入（线性回归的输出）到逻辑回归输出结果0.6，那么这个概率值超过0.5，意味着我们训练或者预测的结果就是A(1)类别。那么反之，如果得出结果为0.3那么，训练或者预测结果就为B(0)类别。
    3）API
        sklearn.linear_model.LogisticRegression(solver='liblinear', penalty=‘l2’, C = 1.0)
            solver:优化求解方式（默认开源的liblinear库实现，内部使用了坐标轴下降法来迭代优化损失函数）
                sag：根据数据集自动选择，随机平均梯度下降
            penalty：正则化的种类
            C：正则化力度
            默认将类别数量少的当做正例
    4）案例
"""
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error

# 标准化
transfer = StandardScaler()


def house_price_predict_LinearRegression():
    """
    加州房价预测-正规方程

    特征名： ['MedInc', 'HouseAge', 'AveRooms', 'AveBedrms', 'Population', 'AveOccup', 'Latitude', 'Longitude']
     [   8.3252       41.            6.98412698    1.02380952  322. 2.55555556   37.88       -122.23      ] 4.526 （单位100,000美元）
     属性信息:
        -街区组收入中位数
        -房屋年龄组别房屋年龄中位数
        -平均房间数每户平均房间数
        - avebedms每户平均卧室数
        - Population块组人口
        -平均家庭成员数
        -纬度块组纬度
        -经度块组经度
    目标名： ['MedHouseVal'] 加州各区的房价中位数
    :return:
    """
    # 1 获取数据集
    house_data = fetch_california_housing(data_home="./data")
    # 2数据预处理
    # 划分特征值和目标值
    x = house_data.get("data")
    y = house_data.get("target")
    # 训练集和测试集划分
    x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=22)

    # 3 特征工程
    # transfer = StandardScaler()
    x_train = transfer.fit_transform(x_train)
    x_test = transfer.transform(x_test)

    # 4 算法训练-模型
    estimator = LinearRegression()
    estimator.fit(x_train, y_train)

    # 5 得出模型
    print("正规方程-权重系数为：\n", estimator.coef_)
    print("正规方程-偏置为：\n", estimator.intercept_)

    # 6 模型评估
    y_predict = estimator.predict(x_test)
    print("准确率：", estimator.score(x_test, y_test))
    # 0.49182677615299203 0.4918267761529808（标准化）
    error = mean_squared_error(y_test, y_predict)
    print("正规方差误差：", error)

    return None


from sklearn.linear_model import SGDRegressor


def house_price_predict_SGDRegressor():
    """
    加州房价预测-梯度下降
    :return:
    """
    # 1 获取数据集
    house_data = fetch_california_housing(data_home="./data")
    # 2数据预处理
    # 划分特征值和目标值
    x = house_data.get("data")
    y = house_data.get("target")
    # 训练集和测试集划分
    x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=22)

    # 3 特征工程
    # transfer = StandardScaler()
    x_train = transfer.transform(x_train)
    x_test = transfer.transform(x_test)

    # 4 算法训练-模型
    estimator = SGDRegressor(max_iter=10000)
    estimator.fit(x_train, y_train)

    # 5 得出模型
    print("梯度下降-权重系数为：\n", estimator.coef_)
    print("梯度下降-偏置为：\n", estimator.intercept_)

    # 6 模型评估
    y_predict = estimator.predict(x_test)
    print("准确率：", estimator.score(x_test, y_test))
    error = mean_squared_error(y_test, y_predict)
    print("梯度下降误差：", error)

    return None


from sklearn.linear_model import Ridge
from sklearn.model_selection import GridSearchCV


def houce_price_predict_Ridge():
    """
    加州房价预测-岭回归
    :return:
    """
    # 1 获取数据集
    house_data = fetch_california_housing(data_home="./data")
    # 2数据预处理
    # 划分特征值和目标值
    x = house_data.get("data")
    y = house_data.get("target")
    # 训练集和测试集划分
    x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=22)

    # 3 特征工程
    # transfer = StandardScaler()
    x_train = transfer.transform(x_train)
    x_test = transfer.transform(x_test)

    # 4 算法训练-模型
    estimator = Ridge()
    super_param = {"alpha": [0.0, 0.5, 1.0, 3.0, 5.0], "max_iter": [2000, 4000, 6000, 8000, 10000]}
    estimator = GridSearchCV(estimator=estimator, param_grid=super_param, cv=10)
    estimator.fit(x_train, y_train)

    # 5 得出模型
    # print("岭回归-权重系数为：\n", estimator.coef_)
    # print("岭回归-偏置为：\n", estimator.intercept_)

    # 6 模型评估
    y_predict = estimator.predict(x_test)
    # 0.6129627045248398
    print("准确率：", estimator.score(x_test, y_test))
    error = mean_squared_error(y_test, y_predict)
    # 0.49182311234443526
    print("岭回归误差：", error)
    return None


if __name__ == '__main__':
    # 加州房价预测-正规方程
    house_price_predict_LinearRegression()

    print("----------------")

    # 加州房价预测-梯度下降
    house_price_predict_SGDRegressor()

    print("----------------")

    # 加州房价预测-岭回归
    houce_price_predict_Ridge()
